package cn.chengpengper.fragmentUpload.service.impl;

import cn.chengpengper.fragmentUpload.common.ServiceExceptionUtils;
import cn.chengpengper.fragmentUpload.dto.ShardUploadCompleteRequest;
import cn.chengpengper.fragmentUpload.dto.ShardUploadDetailResponse;
import cn.chengpengper.fragmentUpload.dto.ShardUploadInitRequest;
import cn.chengpengper.fragmentUpload.dto.ShardUploadPartRequest;
import cn.chengpengper.fragmentUpload.entity.ShardUploadDO;
import cn.chengpengper.fragmentUpload.entity.ShardUploadPartDO;
import cn.chengpengper.fragmentUpload.mapper.ShardUploadMapper;
import cn.chengpengper.fragmentUpload.mapper.ShardUploadPartMapper;
import cn.chengpengper.fragmentUpload.service.ShardUploadService;
import cn.chengpengper.fragmentUpload.utils.ShardUploadUtils;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/*@formatter:off*/

/**
 * ____________________
 * < 神兽护体，永无bug! >
 * --------------------
 *
 * @author catwing
 * @version v1.0.0
 * @date 2024/12/4 22:51
 * @description 分片任务服务实现类
 */

/*@formatter:on*/

@Slf4j
@Service
@RequiredArgsConstructor
public class ShardUploadServiceImpl extends ServiceImpl<ShardUploadMapper, ShardUploadDO> implements ShardUploadService {

    private final String SHARD_FILE_PATH = Objects.requireNonNull(ShardUploadServiceImpl.class.getClassLoader().getResource("")).getPath();

    private final ShardUploadMapper shardUploadMapper;

    private final ShardUploadPartMapper shardUploadPartMapper;

    @Override
    public Long initUploadTask(ShardUploadInitRequest request) {
        ShardUploadDO uploadTask = ShardUploadDO.builder()
                .id(IdUtil.getSnowflakeNextId())
                .fileName(request.getFileName())
                .partNum(request.getPartNum())
                .md5(request.getMd5())
                .build();
        this.save(uploadTask);
        return uploadTask.getId();
    }

    @SneakyThrows
    @Override
    public void uploadPart(ShardUploadPartRequest request) {
        ShardUploadPartDO part = selectUploadPartDO(request.getShardUploadId(), request.getPartOrder());
        if (Objects.nonNull(part)) {
            log.info("Task [{}] With No.[{}] Part Has Been Uploaded", request.getShardUploadId(), request.getPartOrder());
            return;
        }
        // 1、获取子文件路径
        String partFileFullPath = this.getPartFileFullPath(request.getShardUploadId(), request.getPartOrder());
        File partFile = new File(partFileFullPath);
        ShardUploadUtils.createFileNotExists(partFile);
        // 2、分片文件写入磁盘
        request.getFile().transferTo(partFile);
        // 3、保存分片文件上传记录
        this.saveShardUploadPart(request, partFileFullPath);
    }

    private ShardUploadPartDO selectUploadPartDO(Long shardUploadId, Integer partOrder) {
        LambdaQueryWrapper<ShardUploadPartDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShardUploadPartDO::getShardUploadId, shardUploadId)
                .eq(ShardUploadPartDO::getPartOrder, partOrder);
        return this.shardUploadPartMapper.selectOne(queryWrapper);
    }

    @Override
    public void complete(ShardUploadCompleteRequest request) {
        // 1、获取分片任务
        ShardUploadDO uploadTask = this.getById(request.getShardUploadId());
        if (Objects.isNull(uploadTask)) {
            ServiceExceptionUtils.throwException("分片任务不存在");
        }
        // 2、获取分片文件上传记录
        List<ShardUploadPartDO> partList = this.selectShardUploadPartList(request.getShardUploadId());
        if (!Objects.equals(uploadTask.getPartNum(), partList.size())) {
            ServiceExceptionUtils.throwException("分片还未全部上传完成");
        }
        // 3、将分片文件合并成完整文件
        File file = this.mergeFile(uploadTask, partList);
        uploadTask.setFileFullPath(file.getAbsolutePath());
        this.updateById(uploadTask);
    }

    @Override
    public ShardUploadDetailResponse detail(Long shardUploadId) {
        ShardUploadDO uploadTask = this.getById(shardUploadId);
        if (Objects.isNull(uploadTask)) {
            ServiceExceptionUtils.throwException("分片任务不存在");
        }

        List<ShardUploadPartDO> partList = this.selectShardUploadPartList(shardUploadId);
        return ShardUploadDetailResponse.builder()
                .shardUploadId(shardUploadId)
                .partNum(uploadTask.getPartNum())
                .success(Objects.equals(uploadTask.getPartNum(), partList.size()))
                .partOrderList(partList.stream().map(ShardUploadPartDO::getPartOrder).collect(Collectors.toList()))
                .build();
    }

    @SneakyThrows
    private File mergeFile(ShardUploadDO uploadTask, List<ShardUploadPartDO> partList) {
        String fileFullPath = this.getFileFullPath(uploadTask);
        File file = ShardUploadUtils.createFileNotExists(new File(fileFullPath));

        try (FileOutputStream fileOutputStream = FileUtils.openOutputStream(file, true)) {
            for (ShardUploadPartDO part : partList) {
                File partFile = new File(part.getFileFullPath());
                try (FileInputStream partFileInputStream = FileUtils.openInputStream(partFile)) {
                    IOUtils.copyLarge(partFileInputStream, fileOutputStream);
                    log.info("PartFile [{}] Merge Successfully", partFile.getName());
                }
                boolean deleteRes = partFile.delete();
                log.info("PartFile [{}] Delete Result Is [{}]", partFile.getName(), deleteRes);
            }
        }

        //校验合并后的文件和目标文件的md5字是否一致
        if (StringUtils.isNotBlank(uploadTask.getMd5()) && !uploadTask.getMd5().equals(SecureUtil.md5(file))) {
            ServiceExceptionUtils.throwException("文件md5不匹配");
        }
        return file;
    }

    private List<ShardUploadPartDO> selectShardUploadPartList(Long shardUploadId) {
        LambdaQueryWrapper<ShardUploadPartDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShardUploadPartDO::getShardUploadId, shardUploadId)
                .orderByAsc(ShardUploadPartDO::getPartOrder);
        return this.shardUploadPartMapper.selectList(queryWrapper);
    }

    private ShardUploadPartDO saveShardUploadPart(ShardUploadPartRequest request, String partFileFullPath) {
        ShardUploadPartDO part = ShardUploadPartDO.builder()
                .id(IdUtil.getSnowflakeNextId())
                .shardUploadId(request.getShardUploadId())
                .partOrder(request.getPartOrder())
                .fileFullPath(partFileFullPath)
                .build();

        this.shardUploadPartMapper.insert(part);
        return part;
    }

    private String getPartFileFullPath(Long shardUploadId, Integer partOrder) {
        return String.format(SHARD_FILE_PATH + "%s/%s", shardUploadId, partOrder);
    }

    private String getFileFullPath(ShardUploadDO shardUploadPO) {
        return String.format(SHARD_FILE_PATH + "%s/%s", shardUploadPO.getId(), shardUploadPO.getFileName());
    }
}
